前言:rmi(remote method invocation)是java官方的远程调用的是一种实现方式,它使得我们能像调用本地服务一般调用远程服务
源码分析 1. 客户端调用 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import  java.rmi.Naming;public  class  ClientApplication      public  static  void  main (String args[])           String url = "rmi://localhost:8888/" ;         try  {                                       ServerService service = (ServerService) Naming.lookup(url + "server-service" );             Class stubClass = service.getClass();             System.out.println(service + " 是 "  + stubClass.getName() + " 的实例!" );                          Class[] interfaces = stubClass.getInterfaces();             for  (Class c : interfaces) {                 System.out.println("存根类实现了 "  + c.getName() + " 接口!" );             }             System.out.println(service.service());         } catch  (Exception e) {             e.printStackTrace();         }     } } 
分析 1. 在RMI服务注册表中查找服务接口, Naming.lookup(registry url+service name)将会返回 Proxy[ServerService,RemoteObjectInvocationHandler[UnicastRef [liveRef: [endpoint:192.168.1.103:49471 ,objID:[-79051ded:16cec85dc68:-7fff, -7771089289704678398]]]]]
即ServerService的动态代理对象
1 ServerService service = (ServerService) Naming.lookup(url + "server-service" ); 
2. 进入Naming.lookup方程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25   public  static  Remote lookup (String name)        throws  NotBoundException,           java.net.MalformedURLException,           RemoteException    {      ParsedNamingURL parsed = parseURL(name);       Registry registry = getRegistry(parsed);       if  (parsed.name == null )           return  registry;       return  registry.lookup(parsed.name);   } 
1 Registry registry = getRegistry(parsed); 
Registry is a remote interface to a simple remote
 
3. Registry接口帮我们拿到我们想要的指定名称的远程服务的reference 即RegistryImpl_Stub 
进入registry.lookup(parsed.name)
调用 RegistryImpl_Stub的ref(RemoteRef)对象的newCall()方法,将RegistryImpl_Stub对象传了进去,不要忘了构造它的时候我们将服务器的主机端口等信息传了进去,也就是我们把服务器相关的信息也传进了newCall()方法。newCall()方法做的事情简单来看就是建立了跟远程RegistryImpl的Skeleton对象的连接
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public  Remote lookup (String var1)  throws  AccessException, NotBoundException, RemoteException         try  {             RemoteCall var2 = this .ref.newCall(this , operations, 2 , 4905912898345647071L );             try  {                 ObjectOutput var3 = var2.getOutputStream();                 var3.writeObject(var1);             } catch  (IOException var17) {                 throw  new  MarshalException("error marshalling arguments" , var17);             }             this .ref.invoke(var2);             Remote var22;             try  {                 ObjectInput var4 = var2.getInputStream();                 var22 = (Remote)var4.readObject();             } catch  (IOException var14) {                 throw  new  UnmarshalException("error unmarshalling return" , var14);             } catch  (ClassNotFoundException var15) {                 throw  new  UnmarshalException("error unmarshalling return" , var15);             } finally  {                 this .ref.done(var2);             }             return  var22;         } catch  (RuntimeException var18) {             throw  var18;         } catch  (RemoteException var19) {             throw  var19;         } catch  (NotBoundException var20) {             throw  var20;         } catch  (Exception var21) {             throw  new  UnexpectedException("undeclared checked exception" , var21);         }     } 
4. 客户端获取服务端返回的服务Stub对象,接下来可以利用Stub对象进行远程调用 2. 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import  java.rmi.Naming;import  java.rmi.registry.LocateRegistry;public  class  ServerApplication      public  static  void  main (String args[])           try  {                          ServerService service = new  ServerServiceImpl();                                       LocateRegistry.createRegistry(8888 );                          Naming.bind("rmi://localhost:8888/server-service" , service);         } catch  (Exception e) {             e.printStackTrace();         }         System.out.println("服务器向命名表注册了1个远程服务对象!" );     } } 
1. LocateRegistry.createRegistry(8888)创建RegistryImpl对象 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  RegistryImpl (final  int  var1)  throws  RemoteException       this .bindings = new  Hashtable(101 );       if  (var1 == 1099  && System.getSecurityManager() != null ) {           try  {               AccessController.doPrivileged(new  PrivilegedExceptionAction<Void>() {                   public  Void run ()  throws  RemoteException                        LiveRef var1x = new  LiveRef(RegistryImpl.id, var1);                       RegistryImpl.this .setup(new  UnicastServerRef(var1x, (var0) -> {                           return  RegistryImpl.registryFilter(var0);                       }));                       return  null ;                   }               }, (AccessControlContext)null , new  SocketPermission("localhost:"  + var1, "listen,accept" ));           } catch  (PrivilegedActionException var3) {               throw  (RemoteException)var3.getException();           }       } else  {           LiveRef var2 = new  LiveRef(id, var1);           this .setup(new  UnicastServerRef(var2, RegistryImpl::registryFilter));       }   } 
1 2 3 RegistryImpl.this .setup(new  UnicastServerRef(var1x, (var0) -> {     return  RegistryImpl.registryFilter(var0); })); 
setUp()方法将指向正在初始化的RegistryImpl对象的远程引用ref(RemoteRef)赋值为传入的UnicastServerRef对象,这里涉及了向上转型。然后继续移交UnicastServerRef的exportObject()方法。
进入UnicastServerRef的exportObject()方法。可以看到,这里首先为传入的RegistryImpl创建一个代理,这个代理我们可以推断出就是后面服务于客户端的RegistryImpl的Stub对象。然后将UnicastServerRef的skel(skeleton)对象设置为当前RegistryImpl对象。最后用skeleton、stub、UnicastServerRef对象、id和一个boolean值构造了一个Target对象,也就是这个Target对象基本上包含了全部的信息。调用UnicastServerRef的ref(LiveRef)变量的exportObject()方法。
到上面为止,我们看到的都是一些变量的赋值和创建工作,还没有到连接层,这些引用对象将会被Stub和Skeleton对象使用。接下来就是连接层上的了。追溯LiveRef的exportObject()方法,很容易找到了TCPTransport的exportObject()方法。这个方法做的事情就是将上面构造的Target对象暴露出去。调用TCPTransport的listen()方法,listen()方法创建了一个ServerSocket,并且启动了一条线程等待客户端的请求。接着调用父类Transport的exportObject()将Target对象存放进ObjectTable中
2. 将服务实现绑定到服务端的RegistryImpl上,使得客户端只需与RegistryImpl_Stub交互 总结 
当客户端通过RMI注册表找到一个远程接口的时候,所得到的其实是远程接口的一个动态代理对象。 
当客户端调用其中的方法的时候,方法的参数对象会在序列化之后,传输到服务器端 
服务器端接收到之后,进行反序列化得到参数对象 
并使用这些参数对象,在服务器端调用实际的方法 
调用的返回值Java对象经过序列化之后,再发送回客户端 
客户端再经过反序列化之后得到Java对象,返回给调用者 
这中间的序列化过程对于使用者来说是透明的,由动态代理对象自动完成 
 
Stub和Skeleton的作用 
Stub对象做的事情是建立到服务端Skeleton对象的Socket连接。将客户端的方法调用转换为字符串标识传递给Skeleton对象。并且同步阻塞等待服务端返回结果 
Skeleton对象做的事情是将服务实现传入构造参数,获取客户端通过socket传过来的方法调用字符串标识,将请求转发到具体的服务上面。获取结果之后返回给客户端。